Skip to content

IsFileWriteable: probe via permission bits, not append-open#20

Merged
willwade merged 1 commit into
mainfrom
fix/isfilewriteable-no-side-effects
Jun 18, 2026
Merged

IsFileWriteable: probe via permission bits, not append-open#20
willwade merged 1 commit into
mainfrom
fix/isfilewriteable-no-side-effects

Conversation

@willwade

Copy link
Copy Markdown

Why

IsFileWriteable() tested writability by opening the file with ios_base::app | out. Two problems with that:

  1. Side effect: on paths that don't yet exist, it created empty files.
  2. iOS keyboard sandbox noise: the Dasher Apple keyboard extension ships its data bundle (alphabets, colour palettes) inside the read-only .appex container. Every ScanFiles() call probed each scanned file with an append-open, producing a Sandbox: deny file-write-data log line per file from the kernel. Several dozen denies per Realize() call.

What this changes

Replaces the open-and-check with std::filesystem::status() and a permission-bit check for any write bit (owner/group/others).

  • Side-effect free: never creates files.
  • Faster: one stat() instead of open() + close().
  • Sandbox-clean: no write attempts against read-only paths.

Compatibility

Semantically equivalent for all current callers. ScanFiles() only invokes IsFileWriteable() on entries that recursive_directory_iterator already returned as is_regular_file(), so the non-existent-file edge case doesn't arise in practice. The new code handles it anyway (returns false on status() error).

No public API change. No header change. Drop-in for all frontends (Apple, Windows, GTK, WASM).

Verification

Built and verified on iOS via the Dasher Apple keyboard extension. Sandbox denies during Realize() are gone; no regressions in DasherApp, DasherMac, or DasherVision.

The previous implementation tested writability by opening the file with
ios_base::app|out, which:

1. As a side effect, created empty files when probing non-existent paths
   (relevant on platforms where ScanFiles might pass through transient
   candidate paths).
2. On iOS keyboard extensions, where the data bundle is read-only and
   sandboxed, generated a 'file-write-data' sandbox deny for every
   bundled file scanned (colour XMLs, alphabet XMLs, etc.). Each deny
   produced a kernel log line and forced the kernel to set up + tear
   down a denied file descriptor.

Replace the open-with-append with std::filesystem::status(), checking
the permission bits for any write bit (owner/group/others). This is:

- Side-effect free: never creates files.
- Faster: one stat() per file instead of open()+close().
- Sandbox-clean: no write attempts against read-only paths.

Semantically equivalent for all callers (ScanFiles only invokes
IsFileWriteable on paths that already exist as regular files, so the
'non-existent file' case doesn't apply in practice).

Signed-off-by: will wade <willwade@gmail.com>
@willwade willwade merged commit 3ddedde into main Jun 18, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant